package com.pro.framework.api.util;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.ObjUtil;
import com.pro.framework.api.FrameworkConst;
import com.pro.framework.api.clazz.ClassCaches;
import com.pro.framework.api.enums.IEnumInt;
import com.pro.framework.api.model.IModel;
import com.pro.framework.api.model.IModelHasUpdate;
import com.pro.framework.api.structure.Tuple3;
import lombok.SneakyThrows;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;

public class BeanUtils extends BeanUtil {

    public static final Long ID_NOTHING_CHANGE = -10000L;

    public static void copyPropertiesModel(Object o1, Object o2) {
        copyProperties(o1, o2,
                CopyOptions.create().ignoreNullValue().setIgnoreProperties(FrameworkConst.Str.MODEL_IGNORE_PROPERTIES));
    }

    @SuppressWarnings("unchecked")
    public static <T> T convert(String value, Class<T> targetType) {
        if (targetType == null || targetType == String.class) {
            return (T) value;
        }
        if (targetType == BigDecimal.class) {
            return (T) new BigDecimal(value);
        } else if (targetType == BigInteger.class) {
            return (T) new BigInteger(value);
        } else if (targetType == Integer.class || targetType == int.class) {
            return (T) Integer.valueOf(value);
        } else if (targetType == Long.class || targetType == long.class) {
            return (T) Long.valueOf(value);
        } else if (targetType == Double.class || targetType == double.class) {
            return (T) Double.valueOf(value);
        } else if (targetType == Float.class || targetType == float.class) {
            return (T) Float.valueOf(value);
        } else if (targetType == Boolean.class || targetType == boolean.class) {
            return (T) Boolean.valueOf(value);
        } else if (targetType == Short.class || targetType == short.class) {
            return (T) Short.valueOf(value);
        } else if (targetType == Byte.class || targetType == byte.class) {
            return (T) Byte.valueOf(value);
        } else if (targetType == Character.class || targetType == char.class) {
            if (value.length() == 1) {
                return (T) Character.valueOf(value.charAt(0));
            } else {
                throw new IllegalArgumentException("Cannot convert a String with more than one character to char");
            }
        } else if (IEnumInt.class.isAssignableFrom(targetType)) {
            return (T) Arrays.stream(((Class<? extends IEnumInt>) targetType).getEnumConstants())
                    .filter(e -> e.getValue().equals(Integer.valueOf(value)))
                    .findAny()
                    .orElse(null);
        } else if (Enum.class.isAssignableFrom(targetType)) {
            return (T) Arrays.stream(((Class<Enum>) targetType).getEnumConstants())
                    .filter(e -> e.name().equals(value))
                    .findAny()
                    .orElse(null);
        } else {
            throw new IllegalArgumentException("Unsupported target type: " + targetType.getName());
        }
    }

    /**
     * 属性相同就设置为null
     * 如果全部相同 id返回-10000
     *
     * @param oldObj 旧对象
     * @param newObj 新对象
     * @return 需要的更新信息
     */
    @SuppressWarnings("rawtypes")
    @SneakyThrows
    public static <T extends IModelHasUpdate> T buildSaveOrUpdate(T oldObj, T newObj) {
        if (newObj == null) {
            return null;
        }
        Class<?> clazz = newObj.getClass();
        boolean oldNotNull = oldObj != null;
        if (oldNotNull) {
            AssertUtil.isTrue(clazz.equals(oldObj.getClass()), "not the same class");
        }

        //noinspection deprecation,unchecked
        T update = (T) clazz.newInstance();
        boolean changed = false;
        Map<String, Tuple3<Field, Method, Method>> fieldMap = ClassCaches.getClassInfos(clazz);
        for (Map.Entry<String, Tuple3<Field, Method, Method>> entry : fieldMap.entrySet()) {
            Method getter = entry.getValue().getT3();
            Method setter = entry.getValue().getT2();
            Field field = entry.getValue().getT1();

            Object oldVal = oldNotNull ? getter.invoke(oldObj) : null;
            Object newVal = getter.invoke(newObj);
            if (null != newVal && !"id".equals(field.getName()) && !isEquals(oldVal, newVal)) {
                setter.invoke(update, newVal);
                if (!changed) {
                    changed = true;
                }
            }
        }
        update.setId(ObjUtil.defaultIfNull(newObj.getId(), oldNotNull ? oldObj.getId() : null));
        update.setHasUpdate(changed);
        //noinspection unchecked
        newObj.setUpdateOld(oldObj);
        newObj.setHasUpdate(changed);
        return update;
    }

    private static boolean isEquals(Object oldVal, Object newVal) {
        if (oldVal instanceof BigDecimal && newVal instanceof BigDecimal) {
            return ((BigDecimal) oldVal).compareTo((BigDecimal) newVal) == 0;
        }
        return Objects.equals(oldVal, newVal);
    }


    public static <T, PROP> PROP getProp(Function<T, PROP> propFun, T o1, T o2) {
        return getProp(propFun, Arrays.asList(o1, o2));
    }

    public static <T, PROP> PROP getProp(Function<T, PROP> propFun, List<T> objs) {
        for (T obj : objs) {
            if (obj != null) {
                PROP value = propFun.apply(obj);
                if (value != null) {
                    return value;
                }
            }
        }
        return null;
    }

    public static <T extends IModel, F extends IModel> List<F> flatMapBeans(List<T> list, Function<T, ? extends Collection<F>> subListFun) {
        return list.stream().flatMap(t -> subListFun.apply(t).stream()).collect(Collectors.toList());
    }

    /**
     * 扁平化父子集合，并对每个子元素设置 parentId
     */
    public static <T extends IModel, F extends IModel> List<F> flatMapBeansSetPid(List<T> list, Function<T, ? extends Collection<F>> subListFun, BiConsumer<T, F> subFun) {
        return list.stream().flatMap(t -> subListFun.apply(t).stream().peek(f -> subFun.accept(t, f))).collect(Collectors.toList());
    }
}
